using System; 
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using System.Data.Linq; 
using System.Data.Linq.Mapping;
using System.Data.Linq.Provider; 
using System.Linq; 
using System.Data.Linq.SqlClient;
using System.Diagnostics.CodeAnalysis; 
using System.Diagnostics;

namespace System.Data.Linq.SqlClient {
 
    // partions select expressions and common subexpressions into scalar and non-scalar pieces by
    // wrapping scalar pieces floating column nodes. 
    internal class SqlColumnizer { 
        ColumnNominator nominator;
        ColumnDeclarer declarer; 

        internal SqlColumnizer() {
            this.nominator = new ColumnNominator();
            this.declarer = new ColumnDeclarer(); 
        }
 
        internal SqlExpression ColumnizeSelection(SqlExpression selection) { 
            return this.declarer.Declare(selection, this.nominator.Nominate(selection));
        } 

        internal static bool CanBeColumn(SqlExpression expression) {
            return ColumnNominator.CanBeColumn(expression);
        } 

        class ColumnDeclarer : SqlVisitor { 
            HashSet<SqlExpression> candidates; 

            internal ColumnDeclarer() { 
            }

            internal SqlExpression Declare(SqlExpression expression, HashSet<SqlExpression> candidates) {
                this.candidates = candidates; 
                return (SqlExpression)this.Visit(expression);
            } 
 
            internal override SqlNode Visit(SqlNode node) {
                SqlExpression expr = node as SqlExpression; 
                if (expr != null) {
                    if (this.candidates.Contains(expr)) {
                        if (expr.NodeType == SqlNodeType.Column ||
                            expr.NodeType == SqlNodeType.ColumnRef) { 
                            return expr;
                        } 
                        else { 
                            return new SqlColumn(expr.ClrType, expr.SqlType, null, null, expr, expr.SourceExpression);
                        } 
                    }
                }
                return base.Visit(node);
            } 
        }
 
        class ColumnNominator : SqlVisitor { 
            bool isBlocked;
            HashSet<SqlExpression> candidates; 

            internal HashSet<SqlExpression> Nominate(SqlExpression expression) {
                this.candidates = new HashSet<SqlExpression>();
                this.isBlocked = false; 
                this.Visit(expression);
                return this.candidates; 
            } 

            internal override SqlNode Visit(SqlNode node) { 
                SqlExpression expression = node as SqlExpression;
                if (expression != null) {
                    bool saveIsBlocked = this.isBlocked;
                    this.isBlocked = false; 
                    if (CanRecurseColumnize(expression)) {
                        base.Visit(expression); 
                    } 
                    if (!this.isBlocked) {
                        if (CanBeColumn(expression)) { 
                            this.candidates.Add(expression);
                        }
                        else {
                            this.isBlocked = true; 
                        }
                    } 
                    this.isBlocked |= saveIsBlocked; 
                }
                return node; 
            }

            internal override SqlExpression VisitSimpleCase(SqlSimpleCase c) {
                c.Expression = this.VisitExpression(c.Expression); 
                for (int i = 0, n = c.Whens.Count; i < n; i++) {
                    // Don't walk down the match side. This can't be a column. 
                    c.Whens[i].Value = this.VisitExpression(c.Whens[i].Value); 
                }
                return c; 
            }

            internal override SqlExpression VisitTypeCase(SqlTypeCase tc) {
                tc.Discriminator = this.VisitExpression(tc.Discriminator); 
                for (int i = 0, n = tc.Whens.Count; i < n; i++) {
                    // Don't walk down the match side. This can't be a column. 
                    tc.Whens[i].TypeBinding = this.VisitExpression(tc.Whens[i].TypeBinding); 
                }
                return tc; 
            }

            internal override SqlExpression VisitClientCase(SqlClientCase c) {
                c.Expression = this.VisitExpression(c.Expression); 
                for (int i = 0, n = c.Whens.Count; i < n; i++) {
                    // Don't walk down the match side. This can't be a column. 
                    c.Whens[i].Value = this.VisitExpression(c.Whens[i].Value); 
                }
                return c; 
            }

            private static bool CanRecurseColumnize(SqlExpression expr) {
                switch (expr.NodeType) { 
                    case SqlNodeType.AliasRef:
                    case SqlNodeType.ColumnRef: 
                    case SqlNodeType.Column: 
                    case SqlNodeType.Multiset:
                    case SqlNodeType.Element: 
                    case SqlNodeType.ScalarSubSelect:
                    case SqlNodeType.Exists:
                    case SqlNodeType.ClientQuery:
                    case SqlNodeType.SharedExpressionRef: 
                    case SqlNodeType.Link:
                    case SqlNodeType.Nop: 
                    case SqlNodeType.Value: 
                    case SqlNodeType.Select:
                        return false; 
                    default:
                        return true;
                }
            } 

            [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "These issues are related to our use of if-then and case statements for node types, which adds to the complexity count however when reviewed they are easy to navigate and understand.")] 
            private static bool IsClientOnly(SqlExpression expr) { 
                switch (expr.NodeType) {
                    case SqlNodeType.ClientCase: 
                    case SqlNodeType.TypeCase:
                    case SqlNodeType.ClientArray:
                    case SqlNodeType.Grouping:
                    case SqlNodeType.DiscriminatedType: 
                    case SqlNodeType.SharedExpression:
                    case SqlNodeType.SimpleExpression: 
                    case SqlNodeType.AliasRef: 
                    case SqlNodeType.Multiset:
                    case SqlNodeType.Element: 
                    case SqlNodeType.ClientQuery:
                    case SqlNodeType.SharedExpressionRef:
                    case SqlNodeType.Link:
                    case SqlNodeType.Nop: 
                        return true;
                    case SqlNodeType.OuterJoinedValue: 
                        return IsClientOnly(((SqlUnary)expr).Operand); 
                    default:
                        return false; 
                }
            }

            internal static bool CanBeColumn(SqlExpression expression) { 
                if (!IsClientOnly(expression)
                    && expression.NodeType != SqlNodeType.Column 
                    && expression.SqlType.CanBeColumn) { 

                    switch (expression.NodeType) { 
                        case SqlNodeType.MethodCall:
                        case SqlNodeType.Member:
                        case SqlNodeType.New:
                            return PostBindDotNetConverter.CanConvert(expression); 
                        default:
                            return true; 
                    } 
                }
                return false; 
            }
        }
    }
} 

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.